package org.vt.eldercare.data;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Formatter;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JLabel;
import javax.swing.JMenu;

import org.jfree.chart.ChartPanel;
import org.jfree.data.time.Millisecond;
import org.vt.eldercare.serial.SerialWriter;
import org.vt.eldercare.serial.TwoWaySerialComm;
import org.vt.eldercare.ui.ChartCollection;
import org.vt.eldercare.ui.NodeNameChangeListener;
import org.vt.eldercare.ui.NodeStatusDisplay;

public class Node {

	private static final int NUM_SAMPLES_FOR_DATA_RATE = 5;

	private int nodeID;

	private String name;

	private ChartCollection charts;

	private List<DataPoint> data = new LinkedList<DataPoint>();

	private Status status;

	private int tempSampleCount = 0;

	private NodeStatusChangeListener statusListener;

	private NodeNameChangeListener nameListener;

	private JLabel nodeListLabel;

	private JMenu nodeExportMenu;

	public JMenu getNodeExportMenu() {
		return nodeExportMenu;
	}

	public void setNodeExportMenu(JMenu nodeExportMenu) {
		this.nodeExportMenu = nodeExportMenu;
	}

	private NodeStatusDisplay statusDisplay;

	private TwoWaySerialComm usbComm;

	public int getTempSampleCount() {
		int returnVal = tempSampleCount;
		tempSampleCount = 0;
		return returnVal;
	}

	public String getName() {
		return name;
	}

	public void setLabel(JLabel label) {
		nodeListLabel = label;
	}

	public void setUSBComm(TwoWaySerialComm comm) {
		usbComm = comm;
	}

	public TwoWaySerialComm getUSBComm() {
		return usbComm;
	}

	public void setStatusDisplay(NodeStatusDisplay display) {
		statusDisplay = display;
	}

	public NodeStatusDisplay getStatusDisplay() {
		return statusDisplay;
	}

	public void setName(String name) {
		this.name = name;
		nodeListLabel.setText(name);
		nodeExportMenu.setText(name);
		nameListener.contentsChanged();
	}

	public int getNodeID() {
		return nodeID;
	}

	public Node(int id) {
		nodeID = id;
		name = "" + id;
		charts = new ChartCollection();
		status = new Status();
	}

	@Override
	public boolean equals(Object n) {
		if (n instanceof Node) {
			return (((Node) n).getNodeID() == this.getNodeID());
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		return getNodeID();
	}

	public void addDataSet(DataPoint point) {
		tempSampleCount++;
		synchronized (data) {
			data.add(point);
		}
		charts.addDataSet(point);
	}

	public ChartPanel getAccelPanel() {
		return new ChartPanel(charts.getAccelChart());
	}

	public ChartPanel getGyroPanel() {
		return new ChartPanel(charts.getGyroChart());
	}

	public void exportDataToFile(File f) throws FileNotFoundException {
		Formatter fmt = new Formatter(f);
		fmt.format("%-4s%-" + (name.length()+4)  + "s%-35s%-25s%-25s%-25s%-25s%-25s%-25s\r\n",
				"ID", "Name", "Date and Time", "aX", "aY", "aZ", "gX", "gY",
				"gZ");
		fmt.flush();
		synchronized (data) {
			for (DataPoint point : data) {
				fmt.format("%-4d%-" + (name.length()+4)
						+ "s%-35s%-25.20f%-25.20f%-25.20f%-25.0f%-25.0f%-25.0f\r\n",
						getNodeID(), getName().trim(), point.getTime()
								.toString(), point.getAccelX(), point
								.getAccelY(), point.getAccelZ(), point
								.getGyroX(), point.getGyroY(), point.getGyroZ());
				fmt.flush();
			}
		}
		fmt.close();
	}

	public double getActualDataRate() {
		synchronized (data) {
			final int loop = (data.size() < NUM_SAMPLES_FOR_DATA_RATE) ? data
					.size() : NUM_SAMPLES_FOR_DATA_RATE;
			final int size = data.size();
			double sum = 0;
			for (int i = 1; i < loop; i++) {
				final Millisecond newM = data.get(size - i).getTime();
				final Millisecond oldM = data.get(size - i - 1).getTime();
				if (newM.getSecond().equals(oldM.getSecond())) {
					sum += 1000 / (newM.getMillisecond() - oldM
							.getMillisecond());
				} else {
					sum += 1000 / (1000 + newM.getMillisecond() - oldM
							.getMillisecond());
				}
			}
			return sum / (double) (loop - 1);
		}
	}

	public class Status {

		public static final boolean DEFAULT_GYRO_X_ENABLED = true;
		public static final boolean DEFAULT_GYRO_Y_ENABLED = true;
		public static final boolean DEFAULT_GYRO_Z_ENABLED = true;
		public static final boolean DEFAULT_ACCEL_X_ENABLED = true;
		public static final boolean DEFAULT_ACCEL_Y_ENABLED = true;
		public static final boolean DEFAULT_ACCEL_Z_ENABLED = true;
		public static final boolean DEFAULT_INACTIVITY_DETECT_ENABLED = true;
		public static final int DEFAULT_INACTIVITY_DETECT_THRESHOLD = 15;
		public static final int DEFAULT_INACTIVITY_WAIT_TIME = 15;
		public static final int DEFAULT_SAMPLES_PER_SECOND = 20;

		private boolean gyroXEnabled = DEFAULT_GYRO_X_ENABLED;
		private boolean gyroYEnabled = DEFAULT_GYRO_Y_ENABLED;
		private boolean gyroZEnabled = DEFAULT_GYRO_Z_ENABLED;
		private boolean accelXEnabled = DEFAULT_ACCEL_X_ENABLED;
		private boolean accelYEnabled = DEFAULT_ACCEL_Y_ENABLED;
		private boolean accelZEnabled = DEFAULT_ACCEL_Z_ENABLED;
		private boolean inactivityDetectEnabled = DEFAULT_INACTIVITY_DETECT_ENABLED;
		private int inactivityThreshold = DEFAULT_INACTIVITY_DETECT_THRESHOLD;
		private int inactivityWaittime = DEFAULT_INACTIVITY_WAIT_TIME;
		private int samples_per_second = DEFAULT_SAMPLES_PER_SECOND;

		public boolean isGyroEnabled() {
			return gyroXEnabled | gyroYEnabled | gyroZEnabled;
		}

		public void setGyroEnabled(boolean gyroEnabled) {
			if (gyroEnabled != isGyroEnabled()) {
				gyroXEnabled = gyroEnabled;
				gyroYEnabled = gyroEnabled;
				gyroZEnabled = gyroEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public int getSamples_per_second() {
			return samples_per_second;
		}

		public void setSamples_per_second(int samples_per_second) {
			this.samples_per_second = samples_per_second;
			statusListener.nodeSampleRateChanged(Node.this);
		}

		public boolean isGyroXEnabled() {
			return gyroXEnabled;
		}

		public void setGyroXEnabled(boolean gyroXEnabled) {
			if (this.gyroXEnabled != gyroXEnabled) {
				this.gyroXEnabled = gyroXEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public boolean isGyroYEnabled() {
			return gyroYEnabled;
		}

		public void setGyroYEnabled(boolean gyroYEnabled) {
			if (this.gyroYEnabled != gyroYEnabled) {
				this.gyroYEnabled = gyroYEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public boolean isGyroZEnabled() {
			return gyroZEnabled;
		}

		public void setGyroZEnabled(boolean gyroZEnabled) {
			if (this.gyroZEnabled != gyroZEnabled) {
				this.gyroZEnabled = gyroZEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public boolean isAccelEnabled() {
			return accelXEnabled | accelYEnabled | accelZEnabled;
		}

		public void setAccelEnabled(boolean accelEnabled) {
			if (isAccelEnabled() != accelEnabled) {
				accelXEnabled = accelEnabled;
				accelYEnabled = accelEnabled;
				accelZEnabled = accelEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public boolean isAccelXEnabled() {
			return accelXEnabled;
		}

		public void setAccelXEnabled(boolean accelXEnabled) {
			if (this.accelXEnabled != accelXEnabled) {
				this.accelXEnabled = accelXEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public boolean isAccelYEnabled() {
			return accelYEnabled;
		}

		public void setAccelYEnabled(boolean accelYEnabled) {
			if (this.accelYEnabled != accelYEnabled) {
				this.accelYEnabled = accelYEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public boolean isAccelZEnabled() {
			return accelZEnabled;
		}

		public void setAccelZEnabled(boolean accelZEnabled) {
			if (this.accelZEnabled != accelZEnabled) {
				this.accelZEnabled = accelZEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public boolean isInactivityDetectEnabled() {
			return inactivityDetectEnabled;
		}

		public void setInactivityDetectEnabled(boolean inactivityDetectEnabled) {
			if (this.inactivityDetectEnabled = inactivityDetectEnabled) {
				this.inactivityDetectEnabled = inactivityDetectEnabled;
				if (statusListener != null)
					statusListener.nodeStatusChanged(Node.this);
			}
		}

		public int getInactivityThreshold() {
			return inactivityThreshold;
		}

		public void setInactivityThreshold(int inactivityThreshold) {
			this.inactivityThreshold = inactivityThreshold;
		}

		public int getInactivityWaittime() {
			return inactivityWaittime;
		}

		public void setInactivityWaittime(int inactivityWaittime) {
			this.inactivityWaittime = inactivityWaittime;
		}
	}

	public Status getStatus() {
		return status;
	}

	public void setNodeStatusChangeListener(NodeStatusChangeListener listener) {
		this.statusListener = listener;
	}

	public void setNodeNameChangeListener(NodeNameChangeListener listener) {
		nameListener = listener;
	}

	public void enableGyroX() {
		if (!getStatus().isGyroXEnabled()) {
			statusDisplay.pendingGyroX();
			usbComm.serialWrite(getNodeID(), SerialWriter.ENABLE_GYRO_X);
		}
	}

	public void enableGyroY() {
		if (!getStatus().isGyroYEnabled()) {
			statusDisplay.pendingGyroY();
			usbComm.serialWrite(getNodeID(), SerialWriter.ENABLE_GYRO_Y);
		}
	}

	public void enableGyroZ() {
		if (!getStatus().isGyroZEnabled()) {
			statusDisplay.pendingGyroZ();
			usbComm.serialWrite(getNodeID(), SerialWriter.ENABLE_GYRO_Z);
		}
	}

	public void disableGyroX() {
		if (getStatus().isGyroXEnabled()) {
			statusDisplay.pendingGyroX();
			usbComm.serialWrite(getNodeID(), SerialWriter.DISABLE_GYRO_X);
		}
	}

	public void disableGyroY() {
		if (getStatus().isGyroYEnabled()) {
			statusDisplay.pendingGyroY();
			usbComm.serialWrite(getNodeID(), SerialWriter.DISABLE_GYRO_Y);
		}
	}

	public void disableGyroZ() {
		if (getStatus().isGyroZEnabled()) {
			statusDisplay.pendingGyroZ();
			usbComm.serialWrite(getNodeID(), SerialWriter.DISABLE_GYRO_Z);
		}
	}

	public void enableAccelX() {
		if (!getStatus().isAccelXEnabled()) {
			statusDisplay.pendingAccelX();
			usbComm.serialWrite(getNodeID(),
					SerialWriter.ENABLE_ACCELEROMETER_X);
		}
	}

	public void enableAccelY() {
		if (!getStatus().isAccelYEnabled()) {
			statusDisplay.pendingAccelY();
			usbComm.serialWrite(getNodeID(),
					SerialWriter.ENABLE_ACCELEROMETER_Y);
		}
	}

	public void enableAccelZ() {
		if (!getStatus().isAccelZEnabled()) {
			statusDisplay.pendingAccelZ();
			usbComm.serialWrite(getNodeID(),
					SerialWriter.ENABLE_ACCELEROMETER_Z);
		}
	}

	public void disableAccelX() {
		if (getStatus().isAccelXEnabled()) {
			statusDisplay.pendingAccelX();
			usbComm.serialWrite(getNodeID(),
					SerialWriter.DISABLE_ACCELEROMETER_X);
		}
	}

	public void disableAccelY() {
		if (getStatus().isAccelYEnabled()) {
			statusDisplay.pendingAccelY();
			usbComm.serialWrite(getNodeID(),
					SerialWriter.DISABLE_ACCELEROMETER_Y);
		}
	}

	public void disableAccelZ() {
		if (getStatus().isAccelZEnabled()) {
			statusDisplay.pendingAccelZ();
			usbComm.serialWrite(getNodeID(),
					SerialWriter.DISABLE_ACCELEROMETER_Z);
		}
	}

	public void enableGyro() {
		if (!getStatus().isGyroEnabled()) {
			statusDisplay.pendingGyro();
			usbComm.serialWrite(getNodeID(), SerialWriter.ENABLE_GYROSCOPE);
		}
	}

	public void disableGyro() {
		if (getStatus().isGyroEnabled()) {
			statusDisplay.pendingGyro();
			usbComm.serialWrite(getNodeID(), SerialWriter.DISABLE_GYROSCOPE);
		}
	}

	public void enableAccel() {
		if (!getStatus().isAccelEnabled()) {
			statusDisplay.pendingAccel();
			usbComm.serialWrite(getNodeID(), SerialWriter.ENABLE_ACCELEROMETER);
		}
	}

	public void disableAccel() {
		if (getStatus().isAccelEnabled()) {
			statusDisplay.pendingAccel();
			usbComm.serialWrite(getNodeID(), SerialWriter.DISABLE_ACCELEROMETER);
		}
	}
}
